<?php

namespace Plugins\AdminBasic\Http\Admin\Service\MakeCURDFiles;

use App\Enums\Booleans;
use Plugins\AdminBasic\Http\Admin\Service\DevAuxService;
use App\Models\Attributes\CreateSql;
use App\Models\Model;
use Carbon\Carbon;
use Hyperf\Stringable\Str;
use Sc\Util\ClassFile\Components\Attribute;
use Sc\Util\ClassFile\Components\ClassFileConstruction;
use Sc\Util\ClassFile\Components\FunctionParam;
use Sc\Util\ClassFile\Components\Method;
use Sc\Util\ClassFile\Components\Out\ValueOut;
use Sc\Util\ClassFile\Components\Property;

/**
 * Class MakeModel
 */
class MakeModel extends AbstractMake
{
    public function make(): string
    {
        $model = new ClassFileConstruction(Str::studly($this->table));
        $model->setNamespace($this->getNamespace('Models'));
        $model->setDocBlock([
            $this->tableComment, "",
            ...self::getCommentByFields($this->fields, $this->table)
        ]);
        $model->addUses(Model::class);
        $model->addUses(Carbon::class);
        $model->addUses(CreateSql::class);

        $this->enumFieldHandle($model);

        $model->setExtends(Model::class);

        $this->addProperties($model);
        $this->addClassAttributes($model);

        $filename = $this->getFilename('Model');

        $this->registerReplace();

        return $this->makeFile($filename, preg_replace('/CreateSql\(<<<EOT(.*?)EOT\)/s', 'CreateSql(<<<SQL$1SQL)', $model->out()));
    }

    public static function getCommentByFields(array $fields, string $table = ''): array
    {
        $template  = ' * @property type $property_name comment';
        $maxLength = self::getFieldsMaxLength($fields);

        $res = [];
        foreach ($fields as $field) {
            $type = 'string';
            if (in_array($field['field_type'], ['double', 'decimal', 'float']) || str_contains($field['field_type'], 'float') || str_contains($field['field_type'], 'decimal')) {
                $type = 'float';
            }else if (!empty($field['status_map'])) {
                $type = MakeEnum::fieldIsBoolean($field['status_map'])
                    ? 'Booleans'
                    : Str::studly($table . 'Enum ' . $field['field']);
            }else if (str_contains($field['field_type'], 'int')) {
                $type = 'int';
            }else if ($field['field_type'] === 'json' || str_ends_with($field['field'], '_ids')) {
                $type = 'array';
            }
            if (in_array($field['field_type'], ['date', 'datetime', 'timestamp', 'time'])) {
                $type = 'Carbon';
            }

            $res[] = strtr($template, [
                'type'          => str_pad($type, 6, ' '),
                'property_name' => str_pad($field['field'], $maxLength, ' '),
                'comment'       => $field['comment']
            ]);
        }
        return $res;
    }

    /**
     * @param array $fields
     *
     * @return int
     */
    public static function getFieldsMaxLength(array $fields): int
    {
        return max(array_map(fn($field) => strlen($field['field']), $fields));
    }

    private function registerReplace(): void
    {
        ValueOut::registerReplace("/'delete_time'\s+=>\s+'int',\s+'update_time'\s+=>\s+'datetime:Y-m-d H:i:s',\s+'create_time'\s+=>\s+'datetime:Y-m-d H:i:s',/", '...self::DEFAULT_CASTS,');
    }

    /**
     * @param ClassFileConstruction $model
     *
     * @return void
     */
    private function addProperties(ClassFileConstruction $model): void
    {
        $model->addProperties(function () {
            $casts = new Property('casts');
            $casts->setType("array");
            $default = [];
            foreach ($this->fields as $field) {
                if ($field['field_type'] === 'json' || str_ends_with($field['field'], '_ids')) {
                    $default[$field['field']] = 'array';
                }
                if ($field['field_type'] === 'datetime') {
                    $default[$field['field']] = 'datetime:Y-m-d H:i:s';
                }
                if ($field['field_type'] === 'date') {
                    $default[$field['field']] = 'date:Y-m-d';
                }
                if ($field['field_type'] === 'timestamp') {
                    $default[$field['field']] = 'timestamp';
                }
                if ($field['field_type'] === 'decimal') {
                    $default[$field['field']] = 'float';
                }
                if (str_contains($field['field_type'], 'int') && empty($field['status_map'])) {
                    $default[$field['field']] = 'int';
                }
            }
            $casts->setDefault([
                ...Model::DEFAULT_CASTS,
                ...$default
            ]);
            $casts->setPublicScope('protected');
            return $casts;
        });


        $model->addProperties(function (){
            $fillable = new Property('fillable');
            $fillable->setType("array");
            $fillable->setDefault(array_values(array_diff(array_column($this->fields, 'field'), ['id', 'create_time', 'delete_time', 'update_time'])));
            $fillable->setPublicScope('protected');
            return $fillable;
        });


        $model->addProperties(function (){
            $table = new Property('table');
            $table->setType("?string");
            $table->setDefault($this->table);
            $table->setPublicScope('protected');
            return $table;
        });

    }

    /**
     * @param ClassFileConstruction $model
     *
     * @return void
     */
    private function addClassAttributes(ClassFileConstruction $model): void
    {
        $model->addAttributes(function () use ($model) {
            $attribute = new Attribute($model->getAppropriateClassName(CreateSql::class));
            $attribute->addParam(DevAuxService::aop()->getCreateSql($this->table));
            return $attribute;
        });
    }

    /**
     * @param ClassFileConstruction $model
     * @return void
     */
    private function enumFieldHandle(ClassFileConstruction $model): void
    {
        foreach ($this->fields as $field) {
            if (empty($field['status_map'])) {
                continue;
            }
            if (MakeEnum::fieldIsBoolean($field['status_map'])) {
                $model->addUses(Booleans::class);
                $model->addMethods($this->enumFieldGetMethod("Booleans", $field['field']));
                $model->addMethods($this->enumFieldSetMethod("Booleans", $field['field']));
                continue;
            }
            $enumName = Str::studly($this->table . 'Enum ' . $field['field']);
            $model->addUses($this->getNamespace("Enums") . "\\" . $enumName);
            $model->addMethods($this->enumFieldGetMethod($enumName, $field['field']));
            $model->addMethods($this->enumFieldSetMethod($enumName, $field['field']));
        }
    }


    private function enumFieldGetMethod(string $enumName, string $fieldName): Method
    {
        $method = new Method("get" . Str::studly($fieldName) . "Attribute");
        $method->addParameters(
            new FunctionParam("value")
        );

        $method->setReturnType($enumName);
        $method->addCode(<<<PHP
        return $enumName::from(\$value);
        PHP);

        return $method;
    }

    private function enumFieldSetMethod(string $enumName, string $fieldName): Method
    {
        $method = new Method("set" . Str::studly($fieldName) . "Attribute");
        $method->addParameters(
            new FunctionParam("value")
        );

        $method->setReturnType('void');
        $method->addCode(<<<PHP
        \$this->attributes['$fieldName'] = \$value instanceof $enumName ? \$value->value : \$value;
        PHP);

        return $method;
    }
}